﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using PDX.Services;

namespace PDX
{
    /// <summary>
    /// A control that binds the DataContext of its content to the
    /// <see cref="PropertyModelProperty"/>.
    /// </summary>
    public class Context : ContentControl, IRaiseDependencyPropertyChanges
    {
        static Context()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Context), new FrameworkPropertyMetadata(typeof(Context)));
        }

        /// <summary>
        /// Occurs when a new <see cref="Context"/> is being constructed.
        /// </summary>
        public static event EventHandler<GenericEventArgs<Context>> OnConstructed = delegate { };

        /// <summary>
        /// Initializes a new instance of the <see cref="Context"/> class.
        /// </summary>
        public Context()
        {
            IsTabStop = false;

            this.Loaded += new RoutedEventHandler(ContextControl_Loaded);
            this.DataContextChanged += (s, e) => Raise(e);

            OnConstructed(this, new GenericEventArgs<Context>(this));
        }

        void ContextControl_Loaded(object sender, RoutedEventArgs e)
        {
            ContentChanged();
        }

        /// <summary>
        /// Called when the <see cref="P:System.Windows.Controls.ContentControl.Content"/> property changes.
        /// </summary>
        /// <param name="oldContent">The old value of the <see cref="P:System.Windows.Controls.ContentControl.Content"/> property.</param>
        /// <param name="newContent">The new value of the <see cref="P:System.Windows.Controls.ContentControl.Content"/> property.</param>
        protected override void OnContentChanged(object oldContent, object newContent)
        {
            base.OnContentChanged(oldContent, newContent);

            ContentChanged();
        }

        void ContentChanged()
        {
            var child = this.Content as FrameworkElement;
            if (child != null)
            {
                // The PropertyModel attached property is assumed to be
                // set locally or higher up in the tree.
                // Since this property inherits, our content will have PropertyModel set on itself.
                // Bind the content's DataContext to the PropertyModel.
                var binding = new Binding();
                binding.RelativeSource = new RelativeSource(RelativeSourceMode.Self);
                binding.Path = new PropertyPath(PropertyModelProperty);
                child.SetBinding(FrameworkElement.DataContextProperty, binding);

                // This is important - the setting of OriginalDataContext has
                // served its purpose and should be forgotten. We don't want
                // child Contexts also using this value.
                Context.SetOriginalDataContext(child, null);
            }
        }



        /// <summary>
        /// Gets or sets the property path. Setting the property path causes
        /// an <see cref="IPropertyModel"/> to be created and set to the
        /// <see cref="PropertyModelProperty"/>.
        /// </summary>
        /// <value>
        /// The property path.
        /// </value>
        public string PropertyPath
        {
            get { return (string)GetValue(PropertyPathProperty); }
            set { SetValue(PropertyPathProperty, value); }
        }

        /// <summary>
        /// DependencyProperty backing for <see cref="PropertyPath"/>.
        /// </summary>
        public static readonly DependencyProperty PropertyPathProperty =
            DependencyProperty.Register("PropertyPath", typeof(string), typeof(Context),
            new UIPropertyMetadata(null, new PropertyChangedCallback(Raiser.Raise)));


        /// <summary>
        /// Gets the data context to use for property resolution (will respect
        /// <see cref="OriginalDataContextProperty"/> if it is set)
        /// </summary>
        /// <returns></returns>
        public object GetDataContext()
        {
            // Determine the object to resolve the property on.
            // We respect the OriginalDataContext if it is set,
            // otherwise we use this.DataContext.
            var dc = GetOriginalDataContext(this);
            if (dc == null)
            {
                dc = DataContext;
            }
            return dc;
        }




        /// <summary>
        /// Gets the property model.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static object GetPropertyModel(DependencyObject obj)
        {
            return obj.GetValue(PropertyModelProperty);
        }

        /// <summary>
        /// Sets the property model.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetPropertyModel(DependencyObject obj, object value)
        {
            obj.SetValue(PropertyModelProperty, value);
        }

        /// <summary>
        /// The PropertyModel (see <see cref="IPropertyModel"/>).
        /// The Content of this instance will be have its DataContext bound to this property.
        /// </summary>
        /// <remarks>
        /// This property is an attached inheriting DependencyProperty, meaning
        /// you could set it anywhere higher up on the tree (and not set the
        /// <see cref="PropertyPath"/> property on the <see cref="Context"/>).
        /// 
        /// Note that the type is actually object rather than <see cref="IPropertyModel"/>.
        /// I'm not sure if this would ever be desired and should consider the implications
        /// of changing it. If it is set to something other than an
        /// <see cref="IPropertyModel"/>, it is likely that many components won't work.
        /// </remarks>
        public static readonly DependencyProperty PropertyModelProperty =
            DependencyProperty.RegisterAttached("PropertyModel", typeof(object), typeof(Context),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));

        /// <summary>
        /// Gets the property model.
        /// </summary>
        /// <returns></returns>
        public IPropertyModel GetPropertyModel()
        {
            return GetValue(PropertyModelProperty) as IPropertyModel;
        }



        /// <summary>
        /// Gets the original data context.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static object GetOriginalDataContext(DependencyObject obj)
        {
            return (object)obj.GetValue(OriginalDataContextProperty);
        }

        /// <summary>
        /// Sets the original data context.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetOriginalDataContext(DependencyObject obj, object value)
        {
            obj.SetValue(OriginalDataContextProperty, value);
        }

        /// <summary>
        /// If this property is set, its value will be used as the target when resolving
        /// <see cref="PropertyPath"/> (instead of this.DataContext).
        /// </summary>
        /// <remarks>
        /// This is used to support, for example, the <see cref="DataForm"/>.
        /// Because it uses an ItemsControl bound to a collection of <see cref="Declaration"/>s,
        /// each <see cref="Context"/> within a single item needs a clean way to get
        /// back to the viewmodel.
        /// </remarks>
        public static readonly DependencyProperty OriginalDataContextProperty =
            DependencyProperty.RegisterAttached("OriginalDataContext", typeof(object), typeof(Context),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits,
                new PropertyChangedCallback(Raiser.Raise)));







        /// <summary>
        /// Gets the declaration.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static Declaration GetDeclaration(DependencyObject obj)
        {
            return (Declaration)obj.GetValue(DeclarationProperty);
        }

        /// <summary>
        /// Sets the declaration.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetDeclaration(DependencyObject obj, Declaration value)
        {
            obj.SetValue(DeclarationProperty, value);
        }

        /// <summary>
        /// The <see cref="Declaration"/> that was used as the source of this
        /// <see cref="Context"/>.
        /// </summary>
        /// <remarks>
        /// Like the <see cref="OriginalDataContextProperty"/>, this is used as
        /// a way to maintain information as the data context is changed. A
        /// <see cref="Declaration"/> can define, for example, custom templates,
        /// so we pass them down via this attached inheriting property for binding purposes.
        /// </remarks>
        public static readonly DependencyProperty DeclarationProperty =
            DependencyProperty.RegisterAttached("Declaration", typeof(Declaration), typeof(Context),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits,
                new PropertyChangedCallback(Raiser.Raise)));




        /// <summary>
        /// Gets the resolution.
        /// </summary>
        /// <returns></returns>
        public IPropertyResolution GetResolution()
        {
            return GetValue(ResolutionProperty) as IPropertyResolution;
        }

        /// <summary>
        /// Sets the resolution.
        /// </summary>
        /// <param name="resolution">The resolution.</param>
        public void SetResolution(IPropertyResolution resolution)
        {
            SetValue(ResolutionProperty, resolution);
        }

        /// <summary>
        /// The <see cref="IPropertyResolution"/> for this Context.
        /// </summary>
        public static readonly DependencyProperty ResolutionProperty =
            DependencyProperty.Register("Resolution", typeof(IPropertyResolution), typeof(Context),
            new UIPropertyMetadata(null, new PropertyChangedCallback(Raiser.Raise)));







        /// <summary>
        /// Occurs when the value of a PDX-related DependencyProperty changes.
        /// </summary>
        public event DependencyPropertyChangedEventHandler DependencyPropertyChanged = delegate { };

        /// <summary>
        /// Raises the <see cref="DependencyPropertyChanged"/> event with the
        /// specified <see cref="System.Windows.DependencyPropertyChangedEventArgs"/>.
        /// </summary>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        public void Raise(DependencyPropertyChangedEventArgs args)
        {
            DependencyPropertyChanged(this, args);
        }

        FrameworkElement IRaiseDependencyPropertyChanges.Self
        {
            get { return this; }
        }
    }
}
